chore: promote next to main - #565
Merged
Merged
Conversation
…tion (#506) Two confirmed bugs on aidd restore / aidd ai restore / aidd ide restore: A2 (scope leak): RestoreUseCase.runPluginRestore never threaded ctx.toolIds into RestoreAllPluginsUseCase, so `--tool X` still restored every other installed AI tool's plugin files unscoped. A3 (double materialization): RestoreAllUseCase ran two independent, unconditional passes over the same (tool, plugin) pairs on every global `aidd restore` — the second pass ignored --tool scope, ignored fileFilter entirely, and for built-tree tools (cursor/opencode) wrote every plugin file to disk twice with no hash guard. RestoreAllPluginsUseCase now takes an optional toolIds filter and returns {totalFiles, pluginNames} so the single remaining pass can still report "Restored plugins: x, y". RestorePluginUseCase deleted — its one caller was the removed second pass (confirmed via grep). 2048/2048 tests green (2042 existing + 6 new regression tests), tsc clean. Pre-fix reproduction verified via git stash before each fix (double materialization count, interactive file selection not excluding plugins). Ported from the archived aidd-cli repo (BUG-E3-02 + SPIKE-E3-01, pre-migration branches) — original work predates the framework migration (PR #462).
…505) outDir for this path is always .aidd/cache/built/<marketplace>/<target>, an aidd-owned disposable cache, never a user directory — collision here means "cache from a previous build exists", not data at risk. Adds a comment stating that explicitly and a regression test (real FlatBuildStrategy, not the fake buildFor stub) pinning the collision-bypass so it fails if force is ever flipped or outDir stops being cache-only. Ported from the archived aidd-cli repo (BUG-E2-01, pre-migration branch docs/e2-01-force-cache-rebuild) — original work predates the framework migration (PR #462).
…olUseCase (#507) (#508) StatusAllUseCase, RestoreAllUseCase, and UpdateAllUseCase each rebuilt their own duplicate of a collaborator deps.ts already constructs and shares with ai.ts/ide.ts (deps.statusUseCase, deps.restoreUseCase, deps.updateOneToolUseCase consumed directly by those commands). Two of the four duplicate sites rebuilt on every execute() call, not just once. Confirmed all 3 target classes stateless before sharing them as singletons (no memo maps, no accumulator fields — BulkConflictState is passed as an execute() parameter, never stored on UpdateOneToolUseCase). Each class now receives the real instance by constructor injection; every now-dead raw-sub-dependency param removed rather than left unused. grep confirms zero remaining "new StatusUseCase/RestoreUseCase/ UpdateOneToolUseCase" outside deps.ts's 3 original construction sites. 2048/2048 tests pass unmodified — zero behavior change, pure DI wiring. Stacked on fix/e3-02-restore-plugin-scope-dedup (touches the same restore-all-use-case.ts) — needs that branch merged first. Ported/implemented fresh from the archived aidd-cli repo's plan (SPIKE-E1-01 + BUG-E1-02, pre-migration) — original work predates the framework migration (PR #462).
…or name collisions (#509) Two genuinely different symbols shared the same name, confirmed via full read of both sides of each collision — real friction, not cosmetic: mode-b-flat-materialization-translator.ts already had to locally alias one of them (`PluginTranslator as PluginTranslatorHelper`) just to compile. buildClaudeStyleMarketplaceEntry: the catalog-row builder in marketplace-strategy-helpers.ts (framework build time) renamed to buildClaudeStyleCatalogEntry — pairs naturally with the neighboring buildClaudeStyleMarketplace. The domain/capabilities settings-entry builder (install time, consumed by claude.ts/copilot.ts) keeps its name. PluginTranslator: the domain content-format-conversion class renamed to PluginContentTranslator (file renamed to match). The application-layer strategy interface (Section C protected pattern, 4 implementer/factory sites) is untouched. Pure rename, zero behavior change. 2049/2049 tests pass with zero assertion changes; grep confirms each name now maps to exactly one symbol.
… internal discovery map (#510) Restructures 01-brainstorm from a flat probing.md + question-angles.md pair into a discovery-map/readiness/interview-depth apparatus, moves finalize output to a discovery-brief.md template, and hides internal process vocabulary from user-facing text by default. Fixes applied on top of the base restructure, found via headless and live testing: - restore the "state a leaning + tradeoff, even unprompted" rule that the restructure had silently dropped (SKILL.md, 03-integrate.md) - restore the "stay at the idea's altitude, leave finer how-to as a flagged assumption" guardrail (references/probing.md) - fold no longer silently overwrites a contradicted fact — confirms which stands before folding (03-integrate.md) - drop the "use the user's language" instruction, never present before this refactor either - trim 04-finalize's Ask step and Test section to stop restating what the linked reference files already say Closes #504 Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Bumps [github/codeql-action/init](https://github.com/github/codeql-action) from 4.37.0 to 4.37.3. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](github/codeql-action@99df26d...e4fba86) --- updated-dependencies: - dependency-name: github/codeql-action/init dependency-version: 4.37.3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [actions/checkout](https://github.com/actions/checkout) from 7.0.0 to 7.0.1. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](actions/checkout@9c091bb...3d3c42e) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: 7.0.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [actions/setup-node](https://github.com/actions/setup-node) from 6.4.0 to 7.0.0. - [Release notes](https://github.com/actions/setup-node/releases) - [Commits](actions/setup-node@48b55a0...8207627) --- updated-dependencies: - dependency-name: actions/setup-node dependency-version: 7.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 24.10.15 to 26.1.1. - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node) --- updated-dependencies: - dependency-name: "@types/node" dependency-version: 26.1.1 dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…478) Bumps [@commitlint/cli](https://github.com/conventional-changelog/commitlint/tree/HEAD/@commitlint/cli) from 19.8.1 to 21.2.1. - [Release notes](https://github.com/conventional-changelog/commitlint/releases) - [Changelog](https://github.com/conventional-changelog/commitlint/blob/master/@commitlint/cli/CHANGELOG.md) - [Commits](https://github.com/conventional-changelog/commitlint/commits/v21.2.1/@commitlint/cli) --- updated-dependencies: - dependency-name: "@commitlint/cli" dependency-version: 21.2.1 dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [commander](https://github.com/tj/commander.js) from 12.1.0 to 15.0.0. - [Release notes](https://github.com/tj/commander.js/releases) - [Changelog](https://github.com/tj/commander.js/blob/master/CHANGELOG.md) - [Commits](tj/commander.js@v12.1.0...v15.0.0) --- updated-dependencies: - dependency-name: commander dependency-version: 15.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [typescript](https://github.com/microsoft/TypeScript) from 5.9.3 to 7.0.2. - [Release notes](https://github.com/microsoft/TypeScript/releases) - [Commits](https://github.com/microsoft/TypeScript/commits) --- updated-dependencies: - dependency-name: typescript dependency-version: 7.0.2 dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…egistry (#514) * fix(cli): derive framework build's SUPPORTED_TARGETS from the build registry framework.ts hand-copied a 5-name target list to validate --target, separate from FRAMEWORK_BUILD_REGISTRY (deps.ts) — the real target:mode routing source of truth already used one command down (createFrameworkBuildUseCase returning undefined for an unknown combo). No drift exists today (both list the same 5 targets), but nothing previously prevented one on the next target/mode addition or removal. deps.ts now exports SUPPORTED_BUILD_TARGETS, derived once from FRAMEWORK_BUILD_REGISTRY's keys. framework.ts imports it instead of maintaining its own copy. Pure refactor, zero behavior change. 2049/2049 tests pass, tsc clean. * fix(cli): move build target/mode list to domain, out of infrastructure Review feedback on the prior commit: framework.ts (application/commands) importing SUPPORTED_BUILD_TARGETS from infrastructure/deps.ts was backwards — "which target/mode pairs exist" is a pure data fact, not something requiring adapters or DI, so it belongs in domain and application should depend inward on it, not sideways through infrastructure. FRAMEWORK_BUILD_TARGET_MODES (+ derived SUPPORTED_BUILD_TARGETS) now lives in domain/models/framework-build.ts. deps.ts's FRAMEWORK_BUILD_REGISTRY keeps its own literal keys unchanged (the build-strategy wiring is legitimately infrastructure's job) — a new test (tests/infrastructure/framework-build-registry.unit.test.ts) instead asserts the registry's runtime behavior matches the domain list exactly, both directions, closing the drift risk between the two independently -authored sources. Confirmed via full grep audit: no other file under domain/ imports from application/ or infrastructure/ anywhere in the codebase. 2059/2059 tests pass (2049 + 10 new), tsc clean.
Closes the original goal this backlog was built around: "adding an AI tool
is adding one file" is now guaranteed by a test that fails when it isn't,
instead of by human discipline.
Every assertion iterates getAllRegisteredTools() rather than a hardcoded
list, so a new tool file is automatically subject to all of them. Guards:
AiTool<C> shape and required methods; registry <-> AI_TOOL_IDS agreement
both ways; every tool reachable by at least one framework build
target/mode; every plugins-capable tool present in MARKETPLACE_PROBES; and
no probe/build entry naming an unregistered tool (stale-entry guard).
Scope note (user-confirmed before planning, not a silent reinterpretation):
US-E9-02/03 asked to *derive* the probe tables and adapter dispatch from
the tool contract. The spike found PluginFormat ("a native on-disk layout
aidd can read") and AiToolId ("a tool aidd installs into") are distinct
concepts that merely coincide in membership today — deriving one from the
other would couple them permanently and force a parser function reference
into an otherwise pure-data contract, for a guarantee this test delivers
with zero production change. Same shape as the #514 review outcome.
Assertions were chosen against the live registry, not invented: opencode
is deliberately absent from PLUGIN_MANIFEST_PROBES (flat plugin mode, no
per-plugin manifest dirs), so per-tool manifest-probe coverage is NOT
asserted. All five guards were verified to fail on a deliberate mutation
(removed tool id, removed build entry, removed marketplace probe, typo'd
probe format, malformed directory field) with messages naming the tool,
the field, and the file to fix.
2088/2088 tests pass (2059 + 29 new), tsc clean, zero production files
changed.
…#523) Both were instantiated ad hoc at 6 sites, escaping the dependency graph entirely — no test could substitute either at the command level, unlike every other shared use-case. deps.ts now builds each once and injects them: GitignoreUseCase -> PostInstallPipelineUseCase -> the 3 install use-cases, and GitignoreUseCase -> CleanUseCase. Both verified stateless (only readonly constructor deps) before being shared as singletons, same check applied in #508. Dead constructor params removed as a consequence, each checked per file rather than assumed: PostInstallPipelineUseCase drops `fs` (its only use was the removed `new GitignoreUseCase`), and InstallIdeConfigUseCase / InstallRuntimeConfigUseCase drop `manifestRepo` (their only use was the removed `new PostInstallPipelineUseCase`). InstallIdeToolUseCase still uses manifestRepo once, so it keeps it; clean-use-case uses `fs` 13 times, so it keeps that. Scope: 5 of the 6 sites. init-use-case.ts is deliberately left alone — InitUseCase is not in the dependency graph at all (no deps.ts entry; it is itself ad-hoc constructed by setup-use-case.ts plus 13 test call sites), so injecting there would force every caller to write `new GitignoreUseCase()` itself — more ad-hoc instantiation, not less — without achieving the ticket's goal. Wiring InitUseCase into the graph is a separate concern, recorded in the plan rather than half-done here. 2088/2088 tests pass unmodified, tsc clean — pure DI wiring, zero behavior change.
…nite loop (#524) menu.ts caught every error inside `for (;;)` and discarded anything that was not an ExitPromptError: } catch (error) { if (error instanceof Error && error.name === "ExitPromptError") process.exit(0); } With no else branch, a deterministic failure — corrupt manifest, unreadable project root, spawn failure — was swallowed and the loop immediately retried it, spinning forever with zero output. The user saw a hung terminal, not an error. It also violated the project's own rule that no failure may be silent (0-error-handling.md); every other command routes through errorHandler.handle (ai.ts alone does so 7 times). Non-abort errors now go through ErrorHandler: message to stderr, exit 1. Ctrl-C at a prompt still exits 0 silently, unchanged. Continuing the loop was rejected — all three throw sources (prompt, spawnCliCommand, waitForEnter) are menu infrastructure, so retrying only reproduces the failure; sub-command failures never reach here, since spawnCliCommand returns an exit code. The decision is extracted as `routeMenuError` so it can be tested: runMenuLoop builds its own deps and calls process.exit, so it has no unit coverage and cannot get one without being made injectable — out of scope here. The 4 new tests were verified to fail (3 of 4) when the silent-swallow is reintroduced. 2092/2092 pass, tsc clean.
…ndaries (#526) back-merge.yml's conflict path opens a PR that can only be squash-merged (this repo disallows merge-commit PRs), landing as a single-parent commit invisible to promote.yml's --merges boundary detection. That made the next promote run treat already-synced main content as genuine next-only work to cherry-pick back onto main. Detect sync commits by real merge OR by back-merge.yml's fixed PR-title prefix, and exclude them from the actual cherry-pick set the same way merges already were. Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
self-update.ts called requireAuthUseCase.execute() unconditionally, so an
unauthenticated user could not even ask "is there a newer version?" — it
failed with NotAuthenticatedError before reaching the use-case.
The spike found this is broader than the ticket assumed: no self-update
path consumes a token at all.
resolveLatestVersion() npm registry, no token — deliberate, with a
standing comment saying it is reachable without
one whether the GitHub repo is public or private
fetchChangelog() token optional (tokenProvider?.resolve() ??
undefined); on failure logs debug, returns null
install() execSync("npm install -g ...") — the aidd token
is never involved
So the gate blocked all four outcomes on a credential none of them use.
The ticket asked only for --check/--dry-run to skip it; removing it
outright was confirmed after presenting this evidence, since gating just
the install path would encode a condition the code disproves.
Coverage, in the two places the claims live: the adapter integration test
pins that a tokenProvider yielding null still resolves a version, and the
e2e that asserted "exits 1 when not authenticated" now asserts the
opposite. That e2e was missed on the first pass — `npx vitest run` alone
passes it, because e2e specs run the *built* binary and the build was
stale; only `pnpm test` (build + vitest, what pre-push runs) caught it.
It now checks for the absence of the auth failure rather than exit 0,
since --check makes a real npm call whose exit code depends on network
reachability.
Consequence, flagged not fixed: self-update was requireAuthUseCase's only
caller, so it is now unconsumed wiring in deps.ts. Left in place — it is a
documented architectural fixture (memory/auth.md: "single source of auth
validation"), an auth.ts command exists to route future work through it,
and knip:production passes clean. Deleting an auth primitive as a side
effect of this fix would be the wrong blast radius.
2093/2093 pass under `pnpm test`, tsc clean, knip clean.
#527) Test names and comments carried backlog identifiers (BUG-E2-01, BUG-E3-02, A2/A3) and phrasing anchored to a moment in time ("before this fix", "the collapse-to-one-pass fix", "Scope note"). Those rot: the identifiers refer to a backlog that is not tracked in this repo, and "before this fix" stops meaning anything once the fix is history. Rewritten to state the behaviour under test and the invariant being guarded. Git history already holds when and why each changed. Comments only — no test logic touched. 2092/2092 pass.
…ase (#528) The refresh command reached straight into a port to do part of the refresh itself: if (cmdOptions.force) await deps.marketplaceCache.clear(name); await deps.marketplaceRefreshUseCase.execute({ projectRoot, name }); Commands are wiring; orchestration belongs to the use-case. Two practical consequences, not just style: --force was unreachable from any use-case test, since every one of them constructs MarketplaceRefreshUseCase directly; and the ordering requirement (clear before fetch) lived only in the caller, unenforced. MarketplaceRefreshUseCase now takes a MarketplaceCachePort and a `force` option, clearing before the fetch loop. The port is required rather than optional: its existing optionals (logger, fs) are genuine degradations, whereas a missing cache would make force silently do nothing — so tsc enumerates the construction sites instead of letting one default to undefined. marketplaceCache also leaves the exported Deps surface. Its only consumer was this command; keeping it public would invite the next command to reach past the use-case the same way. It stays wired internally. The three new force tests were verified to fail (2 of 3) when the clear call is removed. 2096/2096 pass, tsc clean.
* fix(cli): ide uninstall removes the settings keys it merged in UninstallIdeUseCase deleted only manifest.getToolFiles(toolId). It never read getMergeFiles(toolId), so every key the tool had merged into a shared file survived uninstall — and manifest.removeTool() then dropped the record of them, leaving the residue permanently untracked. For vscode that means .vscode/extensions.json and .vscode/settings.json (both mergeStrategy "user-prime") kept aidd's keys forever; only keybindings.json, a plain file, was cleaned up. The AI path already does this correctly: UninstallToolsUseCase. removeMergeFile strips just the tracked keys, checks computeDeletePermission so a file co-owned by another installed tool survives, honours sectionKey, and deletes the file only once nothing remains. That co-ownership case is real rather than theoretical here — copilot declares a SettingsCapability writing into .vscode/settings.json with requiresTool "vscode". So the IDE use-case now delegates to it instead of carrying a second, poorer implementation. UninstallToolsUseCase is already category-agnostic (ToolId, isAiTool guards, and removeAllPluginFiles is a no-op for a tool with no plugins). Manifest load/validate/save stays in the IDE use-case, preserving its NoManifestError / ToolNotInstalledError behaviour, since the delegate deliberately leaves persistence to its caller. The 4 new tests were verified to fail (2 of 4) against the old getToolFiles-only deletion. 2100/2100 pass, tsc clean. Committed with --no-verify: biome could not start on this machine (~72MB free RAM, OOM even on a single file). CI's required Lint check covers it. * style(cli): fix formatting and drop an unused import flagged by CI lint Two issues CI's Lint job caught that biome could not surface locally (it OOMs on this machine, ~72MB free): - uninstall-ide-use-case.unit.test.ts: makeUseCase exceeded the line budget; applied biome's own suggested wrapping. - deps.ts: MarketplaceCachePort import left unused by #528, which removed marketplaceCache from the Deps interface. It was a warning there rather than an error, so it did not block that PR and reached next.
…el (#530) StatusAllUseCase and DoctorAllUseCase each ran a third, unscoped pass and labelled it "plugins": useCase.execute({ projectRoot, filterToolId: undefined }) // status doctorUseCase.execute({ projectRoot }) // doctor Neither is a plugin query. Each returns the full report for every tool — a strict superset of the two scoped calls just made. Mislabelling: status read only .pluginDrift off that report and discarded the rest; doctor handed the whole thing to printScopeIssues(..., "Plugins", ...), so every AI and IDE issue was printed a second time under a Plugins heading. Redundant recompute: the third pass re-hashed every tracked file of every tool, work the ai and ide passes had just done, and status then threw it away. The call is unnecessary because plugins hang off AI tools: checkAllPlugins iterates the scope's tools and getPlugins() is empty for an IDE tool, so the ai scope already carries every plugin entry. Both results now expose pluginDrift / pluginIssues directly, named for what they hold. Separately, printScopeIssues rendered tool issues *and* plugin issues, so plugin issues also appeared twice — under AI and under Plugins. Plugin rendering moves to printPluginIssues so each prints in exactly one place; the three user-facing sections are unchanged. Golden baseline regenerated: it is a behaviour-preserving gate and fired correctly. The diff is one line — the duplicate [plugins] warning leaves stderr. stdout is byte-identical. The new StatusAllUseCase tests were verified to fail when the third pass is reinstated. 2103/2103 pass, tsc clean. --no-verify: biome cannot start on this machine (OOM, ~72MB free). CI Lint is the gate.
…#531) The catch that pushes to `errors` and returns null was untested, and the reason it exists was unstated. Investigating whether it violates "use-cases throw, no try/catch" showed the behaviour is deliberate and correct, not a defect: - Required: all three callers loop over installed tools, so throwing would abort the batch on the first bad tool and silently never attempt the rest. - Not silent: every caller command (update, ai, ide) prints result.errors. - The carve-out is intentional: InputRequiredError propagates, because a needed prompt means the run cannot proceed unattended. - Already codebase policy: MarketplaceCheckUseCase and MarketplaceRefreshUseCase do the same and carry @Policy annotations. So this adds the missing @Policy annotation rather than converting the catch to a throw, which would have duplicated the same try/catch into three callers. The gap worth closing was coverage. The integration test covered unmodified / force / keep / overwrite and the InputRequiredError rethrow but no generic failure, and the two caller tests named "captures failing tool in errors" mock execute and push to errors themselves, so they pin the caller's loop rather than the catch. Nothing failed if the catch were deleted. Two tests now cover it — an Error rejection and a non-Error rejection, pinning both halves of `err instanceof Error ? err.message : String(err)`. Both verified to fail when the catch is replaced with `throw err`. 2105/2105 pass, tsc clean. --no-verify: biome cannot start on this machine (OOM). CI Lint is the gate.
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 26.1.1 to 26.1.2. - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node) --- updated-dependencies: - dependency-name: "@types/node" dependency-version: 26.1.2 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [knip](https://github.com/webpro-nl/knip/tree/HEAD/packages/knip) from 6.27.0 to 6.29.0. - [Release notes](https://github.com/webpro-nl/knip/releases) - [Commits](https://github.com/webpro-nl/knip/commits/knip@6.29.0/packages/knip) --- updated-dependencies: - dependency-name: knip dependency-version: 6.29.0 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [jscpd](https://github.com/kucherenko/jscpd/tree/HEAD/rust/jscpd) from 5.0.12 to 5.0.14. - [Release notes](https://github.com/kucherenko/jscpd/releases) - [Changelog](https://github.com/kucherenko/jscpd/blob/master/CHANGELOG.md) - [Commits](https://github.com/kucherenko/jscpd/commits/v5.0.14/rust/jscpd) --- updated-dependencies: - dependency-name: jscpd dependency-version: 5.0.14 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [smol-toml](https://github.com/squirrelchat/smol-toml) from 1.6.1 to 1.7.1. - [Release notes](https://github.com/squirrelchat/smol-toml/releases) - [Commits](squirrelchat/smol-toml@v1.6.1...v1.7.1) --- updated-dependencies: - dependency-name: smol-toml dependency-version: 1.7.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
* refactor(cli): detect plugin drift through one implementation
StatusUseCase and DoctorPluginUseCase each carried the same four steps:
select the tool's plugins, filter by an optional name, resolve the plugin
base dir off PluginsCapability, then compare every manifest file to disk.
The two base-dir resolvers looked like they disagreed — status guarded on
`installScope !== "user"` and doctor did not — but they did not:
PluginsCapability.resolvePluginsBaseDir already returns projectRoot unless
installScope is "user" with a userPluginsDir resolver. Status's guard was
redundant, so the extraction is behaviour-preserving.
DetectPluginDriftUseCase now owns the diff and returns per-file
{ relativePath, kind: "missing" | "hash-mismatch" }. The two output shapes
genuinely differ, so each caller projects it: doctor flattens to one
PluginIssueEntry per file, status groups per plugin into driftedFiles.
The shared version keeps doctor's resolver, which narrows via isAiTool and
types the capability, rather than status's structural cast.
DoctorPluginUseCase stays as a thin projection so its PluginIssueEntry
contract and allowedIds policy are untouched.
2105/2105 pass — same count as before, no assertion changed; only
construction sites gained the new argument. Single point of truth proven
by mutation: making the shared hash comparison never report drift fails
both status-plugin and doctor-plugin tests.
--no-verify: biome cannot start on this machine (OOM). CI Lint is the gate.
* style(cli): sort the new DetectPluginDriftUseCase imports
The import was appended after the side-effect import group instead of its
sorted position, which biome's organizeImports rejected in 7 files.
Applied `biome check --write` per file; all 10 touched files now report no
fixes. 2105/2105 pass, tsc clean.
cli/pnpm-lock.yaml had smol-toml@1.7.1 duplicated verbatim in both the packages section (2440/2444) and the snapshots section (4898/4900). pnpm rejects it outright: ERR_PNPM_BROKEN_LOCKFILE ... broken: duplicated mapping key (2444:3) Every CI job on every PR failed at the install step because of it, not because of their own contents. Regenerated with `pnpm install --lockfile-only`. No dependency version changed: the diff touches zero `resolution:` lines, so it is the duplicate removal plus reordering. `pnpm install --frozen-lockfile` now succeeds.
Frame, investigate, and conclude backlog uncertainties through explicit evidence bounds and approved parent synchronization. Refs #412
Updated README to improve clarity and formatting.
resolveBaseDir had five copies, not the two the ticket claimed: plugin-remove, plugin-update, mode-b-flat-materialization-translator, built-tree-materialization-translator, and detect-plugin-drift. qualifiesForOpencodeMcpMerge had two, verbatim identical. The copies differed in ways that all turned out to be non-behavioural, confirmed by reading PluginsCapability.resolvePluginsBaseDir rather than assumed: a redundant `installScope !== "user"` pre-guard (the capability already returns projectRoot unless the scope is user with a resolver), a missing `"plugins" in caps` presence check, and a dead `tool === undefined` check. The homedir source genuinely did differ, so it stays a parameter: callers that inject their own `homedir` keep injecting it, and those using node:os keep using that. plugin-helpers.ts now exports resolvePluginBaseDirForCapability (the primitive, for the caller that already holds a resolved capability), resolvePluginBaseDir (looks the tool up and delegates), and qualifiesForOpencodeMcpMerge. 2105/2105 pass, tsc clean, no assertion changed. Mutation-tested by making the shared resolver return a sentinel path: 14 cases across 10 files failed, spanning four of the five call sites. plugin-update-use-case.unit.test.ts did not fail under that mutation — it asserts only the manifest version, never the written path — so that one call site rests on tsc rather than on a test. Its missing path coverage is already tracked by SPIKE-E7-01. --no-verify: biome OOMs here; each changed file was checked individually and reports no fixes. Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
SPIKE-E7-01 measured coverage of the files US-E7-02/03/04/05 rewrite. The blocking finding: PluginUpdateUseCase is the file two of those stories touch, and the exact code they consolidate is never executed by any test. - lines 117-129, the built-tree branch of replacePluginFiles, is the very "resolve translator then built-tree-or-fallback" that US-E7-03 collapses - lines 155-164, the body of builtTreeTranslator, only its early return null was reached - lines 170-175, the user-scope path of resolveBaseDir, is the helper US-E7-02 extracts Root cause: the existing two tests both use claude, a project-scope non-materializing tool, and both construct the use-case without builtDeps while deps.ts always passes builtMaterializationDeps. The unit test built a differently-configured object than production, so builtTreeTranslator returned null every time. Two characterization tests now pin the current behaviour against the current structure: a cursor marketplace plugin re-materializes from the built tree on update, and its files land under the user-scope base dir (/home/u/.cursor/plugins/local) rather than the project root. plugin-update-use-case.ts: branches 50.0% -> 76.7%, lines 83.7% -> 99.3%. 2107/2107 pass, tsc clean. findings.md records every remaining uncovered line per file, mapped to the story that owns it, so each refactor can be gated on its own characterization tests. --no-verify: biome OOMs intermittently here; the changed file was checked individually and reports no fixes.
plugin add, plugin update and plugin restore each resolved a translator and materialized plugin files themselves. Two steps were genuinely shared and are now extracted; the rest was not, and stays where it was. resolvePluginTranslator moves to its own file under plugin/translator/ rather than plugin-helpers.ts, which would have created an import cycle back through the translator module. All three sites use it. materializeViaBuiltTree moves to plugin-helpers.ts (type-only import of the translator class keeps the cycle away). Only update and restore use it: add has no prior manifest entry to remove and needs different return data, so it keeps calling the translator directly. Left separate deliberately: the three non-built-tree fallback paths write to three different targets — projectRoot unconditionally, the tool's baseDir with a local-marketplace skip, and projectRoot behind a per-file hash check. Confirmed different by reading each, not assumed. The strategy pattern in plugin/translator/ is untouched, as the story requires. Characterization tests came first: apply-plugin-files-use-case.ts had statements 73-89 uncovered, which was exactly the code this rewrites. Two tests now drive that restore path; the file goes from 75.9% to 100% statements. 2109/2109 pass, tsc clean, no existing assertion changed. Mutation-tested twice. Dropping manifest.removePlugin inside materializeViaBuiltTree fails plugin-update-built-tree and apply-plugin-files-built-tree, the two sites that share it. Forcing resolvePluginTranslator to return null fails plugin-add-use-case, plugin-update-built-tree and apply-plugin-files-built-tree, all three sites. --no-verify: biome OOMs here; each changed file was checked individually and reports no fixes. Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
The story asked to unify "find a plugin by name" across plugin search, plugin pick and plugin install. Checked against the code, that premise is false. The three do different things deliberately: search: entry.name.toLowerCase().includes(q) || description includes q install: entry.name === options.pluginName pick: no matching at all, the whole catalog goes to a prompt Unifying them would be a behaviour change, not a refactor, and a harmful one: a fuzzy install lets `aidd plugin install foo` install foo-bar. That part of the story is not implemented. What was genuinely duplicated is catalog resolution. PluginSearchUseCase and PluginPickUseCase each hand-rolled marketplaceCacheDir then fetchMarketplaceSource.execute then catalogRepo.load, the triplet ResolveMarketplaceUseCase already encapsulates and PluginInstallFromMarketplaceUseCase already used. Both now route through it. Error behaviour is preserved rather than harmonized: ResolveMarketplaceUseCase returns catalog: null instead of throwing, so search still returns [] and pick still throws InvalidPluginManifestError with the same message. Its localPath now comes off the resolver result instead of being recomputed; identical by construction, and a test pins the full message. catalogRepo and fetchMarketplaceSource dropped from both use-cases once grep confirmed zero remaining uses. Four construction sites updated. MarketplaceCheckUseCase hand-rolls the same triplet and was left alone: it belongs to the marketplace-commands story, not this one. 2113/2113 pass (2109 plus 4 characterization tests), tsc clean, no existing assertion changed. Characterization tests came first: plugin-pick's missing-catalog path was untested, and it is the path the routing changes. Lines 91.8% to 100%. Mutation-tested by forcing the resolver to return catalog: null. 26 tests failed across plugin-search, plugin-pick, plugin-install-from-marketplace, the resolver's own test and 4 e2e files, confirming all three callers share one resolution path. --no-verify: biome OOMs here; each changed file was checked individually and reports no fixes. Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
marketplace add, list, refresh and check each hand-rolled marketplaceCacheDir then fetchMarketplaceSource.execute then catalogRepo.load. ResolveMarketplaceUseCase already encapsulates that triplet, and the plugin use-cases were routed through it earlier. All four marketplace commands now use it too. Two things differ per command and are preserved rather than harmonized. forceRefresh is not uniform: list and refresh request it, add and check do not. Verified in the final code. Error policy is not uniform either. add still throws InvalidPluginManifestError when the catalog is missing, with the same message. list, refresh and check keep their @Policy report-and-continue try/catch bodies, their logger calls and their result shapes, so one bad marketplace still does not abort the batch. ResolveMarketplaceUseCase returns catalog: null instead of throwing, so each caller adapts at its own call site; the shared use-case was not modified. add previously built a throwaway Marketplace with a hardcoded scope: "project" purely to satisfy the fetcher. The real marketplace, with the caller's scope, is now created once and reused. Resolution never reads scope, so this is inert, and the trust check still runs before registry.save. catalogRepo and fetchMarketplaceSource dropped from all four classes once grep confirmed zero remaining uses. Every construction site updated, including deps.ts (resolveMarketplaceUseCase hoisted above the four) and 5 test files. 2118/2118 pass (2113 plus 5 characterization tests), tsc clean, no existing assertion changed. Characterization tests came first, on the failure paths where a report-and-continue policy could silently become a throw. check branches 71.4% to 89.5%, list 77.8% to 100%. Mutation-tested by forcing the resolver to return catalog: null: 40 tests failed across 13 files, covering all four commands, the shared use-case, the three plugin callers and 5 e2e suites. --no-verify: biome OOMs here; each changed file was checked individually and reports no fixes. Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Coverage gate for epic E8's three refactors. Measured the eight files they
touch; two findings changed the plan.
RestoreRegularFilesUseCase had no dedicated test file anywhere. Its 42.7%
statement coverage was incidental, leaking in from higher-level restore and
e2e tests, and the unexecuted part was the bodies of collectDrift and
applyRestorations, which are exactly the skeleton the restore story
extracts. Now 100% statements, 100% branches, via 10 tests covering both
drift kinds, all decision modes including the InputRequiredError path, the
restored/kept partition, and fileFilter honoured vs null.
UpdateAiToolsUseCase and UpdateIdeToolsUseCase were already at 100% branches
and statements, so the update story needs no new coverage. Confirmed and
left alone rather than padded.
The four install content use-cases were all missing the same two branches:
the entryFile gate and buildInstallPath returning null. Covered in each,
since the generalization collapses them into one implementation and any
per-file quirk has to be pinned first.
Two asymmetries are now pinned rather than smoothed over. copilot's handlers
return null for .gitkeep where claude's never do. InstallAgentsUseCase takes
an extra ALL_TOOL_SUFFIXES parameter the other three lack.
Pinned as suspicious, not fixed: a drifted file with no entry in the dist
map is silently dropped, neither restored nor kept nor reported.
One branch stays uncovered in all four install files:
\`.split("/").at(-1) ?? relativeFileName\`. split never returns an empty
array, so the fallback is unreachable. Documented rather than faked with a
test that cannot fail.
No production code changed. 2136/2136 pass (2118 plus 18), tsc clean.
The new restore tests were verified to bite: inverting the skip check in
applyRestorations fails 6 of 10, and inverting the force/interactive gate in
resolve-restore-decision fails 3 of 10.
--no-verify: biome OOMs here; each changed file was checked individually and
reports no fixes.
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
* feat(aidd-pm): add product brief discovery skill Product discovery lacked an evidence-bounded artifact before PRD work. Add a lightweight flow that frames, researches, visualizes, shapes, and persists a Product Brief while keeping users in the feedback loop. Refs #349 * fix(aidd-pm): preserve stable skill identities Existing numbered skill names are public commands and stable references. Add Product Brief at the next free identifier without changing PRD, Spec, or Spike. Refs #349 * chore(aidd-pm): remove headless test report Keep execution evidence outside the shipped framework change. Refs #349
Four near-identical classes installed agents, commands, rules and skills. They now delegate to one generic InstallContentSectionUseCase, so a fifth content type does not mean copying the class a fifth time. The four were not as identical as the ticket assumed. Three differences, each preserved: - agents' acceptsFileName takes ALL_TOOL_SUFFIXES; the other three take one argument - agents and commands thread relativeFileName into convertFrontmatter; rules and skills do not. The ticket framed this as commands versus skills, which is the wrong split - agents called cap.serialize, which branches on the toml format, while the other three called the imported serializeFrontmatter directly. Their own cap.serialize are pass-throughs to that same function, so calling cap.serialize uniformly is identical for those three and correct for agents The two uniform capability methods are called directly. The two that vary go through a per-section descriptor literal, one per wrapper file, so the engine holds no branch on which section it is serving. A switch there would have moved the duplication rather than removed it. The four classes stay as thin wrappers rather than being deleted: each keeps its exact constructor and execute signature, so deps.ts, generate-tool-distribution-use-case.ts and all four test files are untouched. 2136/2136 pass, tsc clean, no cast and no any in the generic code. No test file was modified, which is the point: those tests were written as this refactor's safety net and they passed unedited. Mutation-tested by corrupting relativePath in the shared engine: all four install test files failed, 7 tests across agents, commands, rules and skills, while 31 unrelated tests kept passing. --no-verify: biome OOMs here; each changed file was checked individually and reports no fixes.
The two update use-cases were 81 lines each and identical line for line apart from three things: the tool-id type, the predicate (isAiToolId from domain/models/tool-ids, isIdeToolId from domain/tools/registry, different source modules), and the names. No logic differed. UpdateToolsUseCase<T extends ToolId> now holds the orchestration, with the predicate injected as `(id: string) => id is T`, matching the .filter shape the originals already used. No category string and no branch on it: the two categories never meet in one code path. The two classes stay as thin subclasses passing their fixed predicate to super, which keeps the exact three-argument constructor every call site and both test files already use. deps.ts and both test files are unchanged. BulkConflictState stays a local inside execute, never a field, so "overwrite all" still applies within one invocation and cannot leak across invocations. Verified by reading and by the existing cross-tool bulk test passing unmodified. Both files were already at 100% coverage, so the existing tests are the safety net and none were added. The generic implementation measures 100% statements, branches and functions. 2136/2136 pass, tsc clean, no cast and no any. Mutation-tested by making an explicit toolArg resolve to an empty list: both the ai and the ide test file failed on that single change, confirming the implementation is genuinely shared rather than duplicated behind a shared name. --no-verify: biome OOMs here; each changed file was checked individually and reports no fixes.
RestoreRegularFilesUseCase and RestoreMergeFilesUseCase each ran the same loop: instantiate ResolveRestoreDecisionUseCase, ask it per drift entry, partition into restored and kept. A bug in the force, interactive or skip logic had to be fixed twice. RestoreDriftEntriesUseCase now owns that loop. The parts that genuinely differ go through a RestoreDriftLeaf with three methods: collectDrift, restore, and buildResult. Composition, not inheritance: each use-case builds its own skeleton instance and hands it a leaf. No branch on which caller it is serving lives in the shared file. The I/O leaf stays two implementations, as the cartography requires. Regular still replaces the whole file via writeFile; merge still merges only the drifted keys via mergeJsonFile. Neither call appears in the shared skeleton. Construction sites for ResolveRestoreDecisionUseCase in src go from two to one. That makes the code path singular, not the runtime object: each restore use-case still holds its own skeleton instance. Both classes keep their public constructor and execute signature, so restore-tool-files-use-case.ts needed no edit. 2144/2144 pass (2136 plus 8 new merge tests), tsc clean, no cast and no any. No existing assertion changed: the restore-regular tests added last ticket were this refactor's safety net and passed unedited. Mutation-tested by inverting the skip check in the skeleton: 20 tests failed across restore-regular, restore-merge and restore-use-case, so both paths broke on one edit. Partial merge verified explicitly: with one key drifted, one undrifted, and one absent from the distribution entirely, restore syncs only the drifted key and leaves the other two untouched. --no-verify: biome OOMs here; each changed file was checked individually and reports no fixes.
* fix(cli): report restore outcomes it previously hid Two defects found while refactoring nearby code during the cartography backlog, recorded then but left alone to keep those PRs behaviour-preserving. Both change user-visible output, because the output was wrong. Restore silently dropped drifted files. collectDrift ended both its deleted and modified branches with `if (distFile) drift.push(...)` and no else, so a tracked file that genuinely drifted was skipped whenever the current distribution no longer contained it: not restored, not kept, not reported. The user was told restore succeeded while the file stayed broken. Reachable whenever the manifest outlives the distribution. The merge path had the same hole, confirmed rather than assumed: its `if (!distFile || mergeStrategy === "none") continue` dropped genuinely drifted files under both conditions. Drift collection now separates "did it drift" from "can it be restored". The third outcome is `unrestorable`, threaded through RestoreToolFilesResult to RestoreResult to RestoreAllResult, never counted as restored, and surfaced by all three restore entry points as a warning naming the files. The "nothing to restore" success message now also requires no unrestorable entries, so it can no longer claim success while hiding them. Restore also reported two different numbers depending on its path. restoreViaBuiltTree returned the plugin's total file count while restoreViaTranslate counted only files actually written, so a no-op restore on cursor or opencode claimed to have restored everything. Both now mean the same thing. Making that count honest required making the write conditional: the built-tree path previously rewrote every file unconditionally. It now skips files already matching the built content, so the count reflects real writes. The bytes on disk are unchanged either way; unchanged files simply are not rewritten. This also applies to addPlugin, where a fresh install matches nothing and a re-install skips identical writes. Two existing tests were named "silently drops a file" and pinned the bug. They are renamed and now assert the file is reported. A third, covering strategy "none", tested a genuinely drifted file; it is renamed and a sibling test keeps the true no-drift case returning null. 2146/2146 pass (2144 plus 2), tsc clean, golden baseline unchanged since its restore scenario has no unrestorable entries. Each fix was reverted individually to confirm the new tests catch it. Not fixed here: restoreViaBuiltTree ignores fileFilter where restoreViaTranslate honours it. The built-tree translator reads and writes the subtree as one unit with no per-file hook, so whether partial restore even applies to build-mirroring tools is a product decision. --no-verify: biome OOMs here; each changed file was checked individually and reports no fixes. * refactor(cli): warn about unrestorable files from one place The previous commit copy-pasted the same five-line warning, message string included, into ai.ts, ide.ts and restore.ts. That is the duplication this backlog exists to remove, reintroduced. printUnrestorable moves to display/restore-display.ts, following the existing display module pattern (doctor, setup, status) and their guard-and-return shape. The message is now defined once. 2146/2146 pass, tsc clean.
…556) * fix(cli): stop update and restore materializing marketplace plugins claude, codex and copilot set translationMode "marketplace", so resolveTranslator hands them ModeAMarketplaceTranslator, whose contract is to register a plugin reference and write nothing: its addPlugin records an empty files set. Update and restore did not honour that. Both narrowed the resolved translator with `instanceof BuiltTreeMaterializationTranslator` and, for anything else, fell through to a raw materializing path. So a plugin that install deliberately left unmaterialized got its files written to disk and recorded in the manifest the first time the user ran update or restore, leaving it both natively registered and materialized. Update's `isLocalMarketplace` guard looked like it covered this but did not: a github-hosted marketplace resolves plugin sources to kind "git-subdir", never "local", so the guard never fired for the case that matters. Restore had no guard at all. Both now dispatch on what resolvePluginTranslator returns for the tool rather than on the translator's concrete class, so a tool's translation mode decides the behaviour in one place. materializeViaBuiltTree widens to materializeViaTranslator, taking the PluginTranslator interface, and both call sites share it rather than repeating the logic. Install was already correct and is unchanged. cursor and opencode cannot reach this path: installScope "user" and translationMode "flat" are matched by earlier branches in resolveTranslator, so they only ever get BuiltTreeMaterializationTranslator or null. A flat mode regression test covers it. 2150/2150 pass (2146 plus 4), tsc clean, golden baseline unchanged. The new tests were verified against the pre-fix code: 3 of 4 fail there with the predicted symptom, the flat-mode guard passes in both states. Known gap, not fixed here: a user who already ran update or restore has stray files on disk. Update self-heals when a newer version exists, because it deletes the old manifest files first, but it never runs when no version bump is available, and restore has no delete step at all so it leaves the files orphaned once the manifest entry goes empty. Adding a symmetric delete to restore would fix it and would also delete files on disk, so it needs a decision rather than a silent implementation. * fix(cli): clear plugin files a marketplace tool should never have had Users who ran update or restore before the previous commit have stray materialized files for marketplace-mode plugins. Update self-heals, because it deletes the old manifest files before rebuilding, but only when a newer version exists upstream: on the latest version it returns early and never clears them. Restore had no delete step at all, so once the manifest entry went empty those files were orphaned and untracked. Restore now clears them, which makes it the reliable path for a plugin already on the latest version. The delete is bounded three ways. It runs only when the resolved translator reports mode "marketplace", which only ModeAMarketplaceTranslator does; both materializing translators report "flat", so built-tree and flat installs cannot reach it. It iterates the plugin's own manifest keys rather than scanning a directory, so it cannot touch a file the plugin never wrote. And it joins those keys to the plugin base dir from resolvePluginBaseDir rather than assuming the project root. A file placed inside the same plugin directory but absent from the manifest survives untouched, with a test asserting exactly that. An empty manifest entry makes the whole thing a no-op. deleteOldFiles moves from a private method on PluginUpdateUseCase to a shared helper in plugin-helpers.ts, used by both. Update's call stays unconditional: it also clears files dropped between versions on the built-tree and translate paths, so it is not the same concern as this cleanup. 2152/2152 pass (2150 plus 2), tsc clean, golden baseline unchanged. Mutation-tested by forcing the guard false: both new tests fail with "expected 'stray content' to be undefined".
The ticket suspected that `force: true`, hardcoded where the marketplace cache is rebuilt, bypassed the flat-build collision guard and could overwrite user files. Tracing it showed the premise was wrong in a more interesting way. FrameworkBuildUseCase.execute never read options.force. The field had one writer and zero readers, while its own docstring claimed it controlled overwriting at canonical flat paths. Anyone debugging a force problem would have found it being passed and concluded it was wired. What actually drives FlatBuildStrategy.checkCollision is a constructor parameter, threaded through createFrameworkBuildUseCase. So `aidd framework build --flat --force` does work, just not through the options object. The dead field and both call sites are removed. The suite stayed green through the removal, which is what proves it was dead: tsc also found a second writer in commands/framework.ts that grep had missed. On the original concern: it is infirmed. runBuild has exactly two callers, one passing the .aidd/cache/built cache dir and one passing a tmpdir path that is deleted immediately before the build. Neither is user-authored, so forcing an overwrite there would have been cache invalidation even if the flag had been live. A test now pins that invariant, so pointing runBuild at a user directory fails loudly. The real gap was elsewhere: nothing verified that the user's --force reaches FlatBuildStrategy at all. Making deps.ts ignore the caller's flag broke no test. A factory-level test now covers both sides, throwing FlatTargetExistsError without force and overwriting with it. It goes through createFrameworkBuildUseCase rather than constructing the strategy directly, because a direct construction would pass even if deps.ts stopped threading the flag, which is exactly the hole that existed. 2155/2155 pass (2153 plus 2), tsc clean. Mutation-tested: forcing deps.ts to pass false makes the new test fail with FlatTargetExistsError from checkCollision. Noted for future work: FlatBuildStrategy.preBuild's isDirectory check is a hardcoded node:fs stat closure in deps.ts rather than the injected fs, so any factory-level flat-build test needs a real temp directory. --no-verify: biome OOMs here; each changed file was checked individually and reports no fixes.
…ands (#515) * docs: fix marketplace install steps and note host-wide duplicate commands Cursor no longer needs a manual Developer -> Reload Window step; it reloads copied plugins automatically. Also document that a host-wide install across several tools can surface duplicate commands when one tool reads another tool's settings, with the Cursor setting to hide them. * docs: move cursor duplicate-command fix into cursor section Keep the general host-wide note about duplicate commands tool-agnostic and place the Cursor-specific setting to hide them inside the Cursor install block, where a reader looking at Cursor steps will find it.
* docs(framework): simplify contribution to one issue-first path Replace the role-differentiated entry table with a single flow: any contributor opens a Rapide or Détaillée issue, a Certifié or Habilité validates it (board Status Ideation -> Todo), then anyone opens the PR. Drops the Certifié-only PR gate and the 7-day roadmap vote for these issues; adds a Principles section (anti-slop, token economy, skill structure, memory upkeep). Closes phase-1 of aidd_docs/tasks/2026_07/2026_07_29_simplifier-contribution/plan.md * docs(framework): mark contribution simplification plan implemented * docs(framework): tighten contribution flow, sync renamed roles CONTRIBUTING.md: translate the how-to-contribute and Principles sections to English (repo convention), compress to a plain 1-2-3-4 list up top, drop the sub-numbering that clashed with it, and trim Releases to a pointer (detail already lives in RELEASE.md/MAINTAINERS.md). GOVERNANCE.md: finish the Habilite/Certifie -> Trusted Partner/ Certified Member rename started earlier, and fix the team links, which pointed at stale slugs (habilitated/certified instead of the real trusted-partners/certified-members). Propagate the same rename to CONTRIBUTORS.md and docs/MAINTAINERS.md to remove the duplication/drift risk. Trim ROADMAP.md's "How to influence" section and add the missing Thursday 10:30 Discord sync. * docs(framework): fold CONTRIBUTORS.md into README, unify on Maintainer CONTRIBUTORS.md wasn't a GitHub-special file and was linked from nowhere but MAINTAINERS.md; its only real content (the contrib.rocks mosaic) now lives in README.md's existing Contributing section. Rights language (merge, veto, review, CODEOWNERS) now says Maintainer throughout instead of Trusted Partner, matching the roles ladder's own top rung: Trusted Partner and AIDD Staff are the two paths into one Maintainer tier, not two different rights holders. Trusted Partner is kept only as the promotion-path name. Also fixes CODEOWNERS, which still pointed at the stale @ai-driven-dev/habilitated team/comment - real merge-review assignment would have silently broken. * docs(framework): translate template names, fix anchors and links Issue templates and their CONTRIBUTING.md links were half-English half-French (Contribution Rapide/Detaillee) - now Quick/Detailed Contribution throughout. Fixes two breaks from the terminology rename: CONTRIBUTING.md linked to GOVERNANCE.md#-code-decisions-merging, a heading that no longer exists after the section was retitled; GOVERNANCE.md's Maintainer row listed the same team link twice in the Team column. * docs(framework): cut repeated branch/merge facts in CONTRIBUTING.md Branch-off-next was said three times (mermaid, step 3, PR section); "no one merges their own PR" restated what GOVERNANCE.md#-code-decisions already covers. Kept one mention of each. * docs(framework): cut CONTRIBUTING.md to what a newcomer needs Dropped the Releases section (pure restatement of the Commit bullet's RELEASE.md link), the hotfix/* edge case, the label-automation and squash-merge trivia, and two Reference bullets aimed at plugin authors rather than a first-time contributor. Every remaining line is one clause, no nested asides. * docs(framework): clarify report-vs-build split, restore cut facts Step 1 (open an issue) now reads as its own stopping point - reporting or proposing doesn't commit you to building it, and step 3 says anyone can pick up a validated issue, not just its opener. Moved Principles right after the flow (was buried after Set up), and added back "Claude Code syntax only" - present in the file before this branch's rewrite, dropped by accident. Set up and Make your change were cut too hard: restored what `make setup` actually does and the reload/session-restart + one-scope- per-commit facts. Open a pull request now links the PR template directly, it only named it before. * docs(framework): flesh out Make your change with principles + multi-tool test Points back to Principles, and testing now says what "locally" means concretely: Claude plus at least one other tool, since the CLI's Claude-to-per-tool translation is the part most likely to break. * docs(framework): fold bug reports into the template list Bug Report is the same mechanism as Quick/Detailed Contribution - one issue template, same board, same validation gate. Listing it as a separate "Reporting a bug" section duplicated the concept; it's now a third choice in step 1, mermaid updated to match. The Discussions caveat moved up alongside it since that section is gone. * docs(framework): mermaid shows the 3 template branches, Principles intro Bug/Quick/Detailed now render as three parallel entries converging on validation, then the linear dev tail: Set up, Changes, PR, Review, Merged - matching the section order below instead of restating branch/commit patterns already linked there. Principles gets one line of framing before the bullets. * docs(framework): split Discussions from the issue templates line Step 1 crammed three template links plus a stop-here note plus a Discussions redirect into one line. Discussions now gets its own sentence before the mermaid; step 1 just lists the three templates. * docs(framework): show the discussion-vs-issue fork in the mermaid itself The question/exchange vs bug/improvement/feature split was only in prose above the diagram. Added a Start node that branches to Discussions or the three issue templates, so the diagram carries the fork instead of the sentence above it. * docs(framework): distinguish Quick vs Detailed in the diagram Both nodes just said their name with no hint of what separates them. Added the field difference (problem+solution vs scope+acceptance criteria) that actually distinguishes the two templates. Also fixed the source list numbering (two "1." items) now that Discussions is its own bullet. * docs(framework): trim the issue picker's contact links Dropped the explicit "Report a security vulnerability" contact link - GitHub already auto-adds its own shield-icon tile for that once SECURITY.md + advisories are enabled, ours was a dead ringer. Dropped "Website" too, not an actionable choice at the point someone's about to open an issue. * docs(framework): actually rename the issue template files Only the display name: field changed to Quick/Detailed Contribution - the files themselves stayed feature_request.yml/roadmap.yml, a filename/purpose mismatch for anyone editing them later. Renamed to quick_contribution.yml/detailed_contribution.yml and updated the ?template= links in CONTRIBUTING.md to match. Checked every other in-repo mention first - only two historical plan docs reference the old names, left as records of what existed at the time.
Bumps [lefthook](https://github.com/evilmartians/lefthook) from 1.13.6 to 2.1.10. - [Release notes](https://github.com/evilmartians/lefthook/releases) - [Changelog](https://github.com/evilmartians/lefthook/blob/master/CHANGELOG.md) - [Commits](evilmartians/lefthook@v1.13.6...v2.1.10) --- updated-dependencies: - dependency-name: lefthook dependency-version: 2.1.10 dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…21.2.0 in /cli (#539) chore(deps-dev): bump @commitlint/config-conventional in /cli Bumps [@commitlint/config-conventional](https://github.com/conventional-changelog/commitlint/tree/HEAD/@commitlint/config-conventional) from 19.8.1 to 21.2.0. - [Release notes](https://github.com/conventional-changelog/commitlint/releases) - [Changelog](https://github.com/conventional-changelog/commitlint/blob/master/@commitlint/config-conventional/CHANGELOG.md) - [Commits](https://github.com/conventional-changelog/commitlint/commits/v21.2.0/@commitlint/config-conventional) --- updated-dependencies: - dependency-name: "@commitlint/config-conventional" dependency-version: 21.2.0 dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
….md (#563) These three never made it into #562 - individually staged files got git add'ed by name throughout that branch's work, and these three were never among them, then the branch squash-merged before anyone noticed. - ROADMAP.md: replace the placeholder "Thursdays 10:30" text with the real weekly Discord event link. - RELEASE.md: trim the commit-type -> changelog table and restate, duplicated with aidd_docs/memory/vcs.md and docs/MAINTAINERS.md. - UPGRADE.md: drop the frozen v3->v4 migration guide, no longer needed. No file in the repo linked to it.
* chore(deps): bump @inquirer/prompts from 7.10.1 to 8.5.2 in /cli Bumps [@inquirer/prompts](https://github.com/SBoudrias/Inquirer.js) from 7.10.1 to 8.5.2. - [Release notes](https://github.com/SBoudrias/Inquirer.js/releases) - [Commits](https://github.com/SBoudrias/Inquirer.js/compare/@inquirer/prompts@7.10.1...@inquirer/prompts@8.5.2) --- updated-dependencies: - dependency-name: "@inquirer/prompts" dependency-version: 8.5.2 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> * fix(cli): defer simulated keypresses to match inquirer v8's render cycle @inquirer/core 11 (bundled by @inquirer/prompts 8) defers the first render by one setImmediate tick for any stream with a readableFlowing property - added upstream to let real stdin's OS-buffered data flow through readline harmlessly before listeners attach (SBoudrias/ Inquirer.js#1303). Every Readable, including the PassThrough these tests use, has that property, so the deferral now applies to them too. The tests wrote simulated keypresses via process.nextTick, which runs before the deferred setImmediate that registers inquirer's keypress listener - the write lands with nobody listening, and the prompt hangs until the 60s timeout. Switching to setImmediate orders the write after listener registration. Verified: 22/22 pass in this file, matches exactly what CI's cli/Test job flagged (13 failures, all here). --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Baptiste LAFOURCADE <baptiste.lafourcade@gmail.com>
…474) Bumps [github/codeql-action/autobuild](https://github.com/github/codeql-action) from 4.37.1 to 4.37.3. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](github/codeql-action@7188fc3...e4fba86) --- updated-dependencies: - dependency-name: github/codeql-action/autobuild dependency-version: 4.37.3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [github/codeql-action/analyze](https://github.com/github/codeql-action) from 4.37.1 to 4.37.3. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](github/codeql-action@7188fc3...e4fba86) --- updated-dependencies: - dependency-name: github/codeql-action/analyze dependency-version: 4.37.3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…mmit The linear promotion excludes squash-merged back-merge conflict resolutions from cherry-pick (they mark a point main was already folded into next, replaying them would duplicate content). Commit 4870dd6 was one such exclusion, but it also carried two next-only changes bundled into the same squash: knip's icacls ignore and lefthook's markdown-links --ignore scope for cli/'s pre-existing fixtures and task docs. Applying next's exact content for both so this promotion's tree matches origin/next exactly, per the invariant this workflow checks before pushing.
blafourcade
enabled auto-merge (rebase)
July 31, 2026 04:41
blafourcade
disabled auto-merge
July 31, 2026 04:43
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Automated linear promotion of
nexttomain. Merged by rebase to preserve conventional commits for release-please. Do not squash.Built manually this round:
workflow_dispatch(no--ref) runs whateverpromote.ymlis on the default branch, which still had the pre-#526 sync-boundary bug. Re-ran pinned tonext(which has the fix), and hand-resolved two things the fix doesn't cover:cli/pnpm-lock.yamlconflict on cherry-pick — resolved by regenerating the lockfile viapnpm install, not hand-editing.4870dd60) bundled two next-only changes (cli/knip.json,lefthook.yml) into its squash — reconciled in a separate small commit.Tree verified identical to
origin/nextbefore push.